Friday Code and Notes (Week 2)
Table of Contents
1 Promela code
1.1 Attempt 1
#define MutexDontCare #include "critical2.h" bit turn = 0; proctype P() { do :: non_critical_section(); waitp: turn == 0; csp: critical_section(); turn = 1 od } proctype Q() { do :: non_critical_section(); waitq: turn == 1; csq: critical_section(); turn = 0 od } init { run P(); run Q(); } ltl mutex { always !(P@csp && Q@csq)} ltl waitp { [] (P@waitp implies (<> P@csp)) } ltl waitq { [] (Q@waitq implies (<> Q@csq)) }
1.2 Attempt 2
#define MutexDontCare #include "critical2.h" bit wantp = 0; bit wantq = 0; proctype P() { do :: non_critical_section(); waitp: wantq == false; wantp = true; csp: critical_section(); wantp = false; od } proctype Q() { do :: non_critical_section(); waitq: wantp == false; wantq = true; csq: critical_section(); wantp = true; od } init { run P(); run Q(); } ltl mutex { [] !(P@csp && Q@csq)} ltl waitp { [] (P@waitp implies (eventually P@csp)) } ltl waitq { [] (Q@waitq implies (<> Q@csq)) }
1.3 Attempt 3
You can do safety verification with "invalid endstates" enabled to find the deadlock here.
#define MutexDontCare #include "critical2.h" bit wantp = 0; bit wantq = 0; proctype P() { do :: non_critical_section(); wantp = true; waitp: wantq == false; csp: critical_section(); wantp = false; od } proctype Q() { do :: non_critical_section(); wantq = true; waitq: wantp == false; csq: critical_section(); wantq = false; od } init { run P(); run Q(); } ltl mutex { [] !(P@csp && Q@csq)} ltl waitp { [] (P@waitp implies (eventually P@csp)) } ltl waitq { [] (Q@waitq implies (<> Q@csq)) }
1.4 Attempt 4
#define MutexDontCare #include "critical2.h" bit wantp = 0; bit wantq = 0; proctype P() { do :: non_critical_section(); waitp: wantp = true do :: wantq -> wantp = false; wantp = true; :: else -> break od csp: critical_section(); wantp = false; od } proctype Q() { do :: non_critical_section(); waitq: wantq = true do :: wantp -> wantq = false; wantq = true; :: else -> break od csq: critical_section(); wantq = false; od } init { run P(); run Q(); } ltl mutex { [] !(P@csp && Q@csq)} ltl waitp { [] (P@waitp implies (eventually P@csp)) } ltl waitq { [] (Q@waitq implies (<> Q@csq)) }
1.5 Attempt 5
Attempt 5 is Dekker's algorithm, the first known correct solution to the critical section problem.
To verify eventual entry, you need to enable weak fairness to rule out traces where, after entering the pre-protocol, a process is never scheduled.
#define MutexDontCare #include "critical2.h" bit wantp = 0; bit wantq = 0; bit turn = 0; proctype P() { do :: non_critical_section(); waitp: wantp = true do :: wantq -> if :: turn == 1 -> wantp = false; turn == 0; wantp = true; :: else -> skip fi :: else -> break od csp: critical_section(); turn = 1; wantp = false; od } proctype Q() { do :: non_critical_section(); waitq: wantq = true do :: wantp -> if :: turn == 0 -> wantq = false; turn == 1; wantq = true; :: else -> skip fi :: else -> break od csq: critical_section(); turn = 0; wantq = false; od } init { run P(); run Q(); } ltl mutex { [] !(P@csp && Q@csq)} ltl waitp { [] (P@waitp implies (eventually P@csp)) } ltl waitq { [] (Q@waitq implies (<> Q@csq)) }
1.6 Critical section boilerplate
The above files use a critical2.h header file,
whose contents are as follows.
/* Definitions for critical section; derived from Ben-Ari's. If PID is defined, prints _pid in CS, otherwise prints a character parameter. If MutexDontCare is defined, no assertion is made about the number of processes in their CSs. */ #ifndef MutexDontCare byte critical = 0; #endif #define PID #ifdef PID inline critical_section() { printf("MSC: %d in CS\n", _pid); #else inline critical_section(proc) { printf("MSC: %c in CS\n", proc); #endif #ifndef MutexDontCare critical++; assert (critical == 1); critical--; #endif } /* Definitions for non-critical sections to complement M Ben-Ari's critical.h. If PID is defined, prints _pid in non-CS, otherwise prints its mandatory character argument. */ #ifdef PID inline non_critical_section() { printf("MSC: %d in non-CS\n", _pid); #else inline non_critical_section(proc) { printf("MSC: %c in non-CS\n", proc); #endif do /* non-deterministically choose how long to stay, even forever */ :: true -> skip :: true -> break od }
2 Notes
In a transition label
g; f
when g = ⊤, we just write f
When there's no state update (when f = I "the identity function"),
then we write g
Q: do we allow Σ to be infinite? Yes.
Σ is the set of all possible states.
What is a state?
A: we don't bother specifying, because it's application dependent
usually, at least:
- a mapping from variable names to values
- which location every process is at
Q: when we change the state, we also update the location, right?
or: when we change the location, we change the state?
A: yes
== Floyd's method on the first transition diagram
P stands for the whole transition diagram.
aka the triangular number program
We want to prove this Hoare triple:
{⊤} P {s = N*(N-1)/2}
We must find an *annotation* for every location.
The annotation should state something that's
always true when we're at that location.
1 The precondition implies the
start location's annotation
2 The exit location's annotation
implies the postcondition.
3 If the current location's annotation is true,
then if we take a transition, the next
location's annotation becomes true.
Annotations (this is called an *assertion network*)
l0: ⊤
l1: i = 0
l2: s = i*(i-1)/2
l3: s = i*(i+1)/2
l4: s = N*(N-1)/2
Observe: the first two points above are trivial
Examples of point 3.
Consider the transition from l1 to l2.
We must prove
i = 0 ⇒ (s = i*(i-1)/2)[s:=0]
that is:
i = 0 ⇒ (0 = i*(i-1)/2) <-- trivial
Consider the transition from l2 to l3
We must prove
s = i*(i-1)/2 ∧ i ≠ N ⇒ s+i = i*(i+1)/2
You can prove this with some basic arithmetic
Q(l_i) ∧ g ⇒ Q(l_j) o f
o denotes function composition.
(f o g)(x) = f(g(x))
"If the annotation at l_i is true,
and the guard is true,
then, after updating the state with f,
the annotation at l_j is true"
p1: wantp = 0
p2: wantp = 0
p3: wantp ≠ 0
p4: wantp ≠ 0 ∧ wantq ≠ wantp
p5: ⊤
q1: wantq = 0
q2: wantq = 0
q3: wantq ≠ 0
q4: wantq ≠ 0 ∧ wantp ≠ -wantq
q5: ⊤
Step 1: check that these annotations are inductive (separately)
Step 2: check for interference
Suppose P is sitting at location p1. Therefore, wantp=0.
We need to check that no action that Q could take
falsifies wantp=0. Trivial because Q never changes wantp.
Suppose instead P is sitting at p4.
wantp ≠ 0 ∧ wantq ≠ wantp
We need to check that nothing Q can do falsifies this annotation.
There are two actions that are relevant are q2 and q5.
For q5:
wantp ≠ 0 ∧ wantq ≠ wantp ∧ ⊤ ⇒ wantp ≠ 0 ∧ 0 ≠ wantp (trivial)
For q2:
wantp ≠ 0 ∧ wantq ≠ wantp ∧ wantq = 0 ∧ wantp = -1 ⇒ wantp ≠ 0 ∧ 1 ≠ wantp
..and one more.
One final step: mutual exclusion holds.
Show that the annotations of p4 and q4 cannot both be true.
Not possible because if so, wantp and wantq would have
simultaneously different and equal polarity.
